/*
 *  ifxconenction.cpp - Informix dbExpress connection implementation
 *
 *    Copyright (c) 2001-2006, Luxena Software Company. All rights reserved.
 *
 *  Purpose:
 *
 */

#include "ifxconnection.hpp"

#include <new>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <int8.h>
#include <sqltypes.h>
#include <sqlhdr.h>
#include <sqliapi.h>
#include <srvinfo.h>
#include "ifxdefs.hpp"
#include "ifxerror.hpp"
#include "ifxconversion.hpp"
#include "ifxcommand.hpp"
#include "ifxmetadata.hpp"
#include "license.hpp"


/////////////////////////////////////////////////////////////////////////////
// CInformixConnection

CInformixConnection::CInformixConnection (CInformixDriver *driver): _driver(0)
{
	(_driver = driver)->AddRef();
	_errorRegister = this;
	_hasActivity = false;
	memset(&_savedConnectionArea, 0, sizeof(_savedConnectionArea));
	_autoCommit = true;
	_blobSize = driver->getBlobSize();
	_waitOnLocks = 0;
	_commitRetain = IFX_COMMIT_RETAIN_FLAG;
	_trimChar = false;
	_catalogName = "";
	_clientLocale = 0;
	_databaseLocale = 0;
	_isolationLevel = IFX_COMMITED_READ;
	_threadMode = INTERTHREADED_FETCH_OPTIMIZED;
}

CInformixConnection::~CInformixConnection ()
{
	if (_driver == 0)
		return;
	disconnect();
	_driver->Release();
}

CInformixActivityPtr CInformixConnection::activate (bool allowAutoCommit, bool saveConnectionArea, bool forFetch)
{
	if (_hasActivity)
		return CInformixActivityPtr();

	makeCurrent();

	CInformixActivityPtr activity(new CConnectionActivity(this));

	if ((!forFetch && _threadMode == INTERTHREADED_FETCH_OPTIMIZED) || _threadMode == INTERTHREADED)
		activity.reset(new CAutoDormantDecorator(this, activity));

	if (allowAutoCommit && _isAnsiDatabase && !_isInTransaction && _autoCommit)
		activity.reset(new CAutoCommitDecorator(activity));

	if (saveConnectionArea)
		activity.reset(new CConnectionAreaDecorator(this, activity));

	return activity;
}

void CInformixConnection::setAutoCommit (bool value)
{
	_autoCommit = value;
}

int4 CInformixConnection::getBlobSize ()
{
	return _blobSize;
}

void CInformixConnection::setBlobSize (int4 value)
{
	_blobSize = value;
}

CCallback * CInformixConnection::getCallback ()
{
	if (_callback.getHandler() == 0)
		return 0;
	return &_callback;
}

void CInformixConnection::setClientLocale (const char *value)
{
	_clientLocale = value;
}

int4 CInformixConnection::getCommitRetain ()
{
	return _commitRetain;
}

void CInformixConnection::setCommitRetain (int4 value)
{
	_commitRetain = value ? IFX_COMMIT_RETAIN_FLAG : 0;
}

void CInformixConnection::setDatabaseLocale (const char *value)
{
	_databaseLocale = value;
}

void CInformixConnection::setIsolationLevel (eXILType value, bool retainUpdateLocks)
{
	switch (value)
	{
	case xilDIRTYREAD:
		_isolationLevel = retainUpdateLocks ? IFX_DIRTY_READ IFX_RETAIN_UPDATE_LOCKS : IFX_DIRTY_READ; break;

	case xilREADCOMMITTED:
		_isolationLevel = retainUpdateLocks ? IFX_COMMITED_READ IFX_RETAIN_UPDATE_LOCKS : IFX_COMMITED_READ; break;

	case xilREPEATABLEREAD:
		_isolationLevel = retainUpdateLocks ? IFX_REPEATABLE_READ IFX_RETAIN_UPDATE_LOCKS : IFX_REPEATABLE_READ; break;

	case xilCUSTOM:
		_isolationLevel = retainUpdateLocks ? IFX_CURSOR_STABILITY IFX_RETAIN_UPDATE_LOCKS : IFX_CURSOR_STABILITY; break;

	default:
		throw CInformixDriverError("Invalid isolation level");
	}

	if (isConnected()) {
		CInformixActivityPtr activity = activate(false, true);
		applyIsolationLevel();
	}
}

bool CInformixConnection::getIsOnLine ()
{
	return _isOnLine;
}

void CInformixConnection::setIsOnLine (bool value)
{
	_isOnLine = value;
}

const char * CInformixConnection::getServerVersion ()
{
	if (_serverVersion == 0) {
		CInformixActivityPtr activity = activate(false, false);
		ifx_srvinfo_t srvInfo = {0};
		checkInformixResult(ifx_srvinfo(&srvInfo));
		_serverVersion = srvInfo.ProdVersion;
	}

	return _serverVersion;
}

void CInformixConnection::setThreadMode (ThreadMode value)
{
	_threadMode = value;
}

bool CInformixConnection::getTrimChar ()
{
	return _trimChar;
}

void CInformixConnection::setTrimChar(bool value)
{
	_trimChar = value;
}

CInformixTypesMapping * CInformixConnection::getTypesMapping ()
{
	return &_typesMapping;
}

bool CInformixConnection::isUniversal ()
{
	if (_isUniversalServer == -1) {
		CInformixActivityPtr activity = activate(false, false);
		_isUniversalServer = ifx_isius();
	}

	return _isUniversalServer != 0;
}

void CInformixConnection::setWaitOnLocks(int value)
{
	_waitOnLocks = value;
	if (isConnected()) {
		CInformixActivityPtr activity = activate(false, true);
		applyWaitOnLocks();
	}
}

void CInformixConnection::attachDatabaseChangeObserver (IDatabaseChangeObserver *observer)
{
	_databaseChangeObservers.push_back(observer);
}

void CInformixConnection::detachDatabaseChangeObserver (IDatabaseChangeObserver *observer)
{
	_databaseChangeObservers.remove(observer);
}

void CInformixConnection::registerDatabaseChange ()
{
	refreshFeatures();
	applyIsolationLevel();
	DatabaseChangeObservers::iterator it = _databaseChangeObservers.begin();
	for (; it != _databaseChangeObservers.end(); ++it)
		(*it)->databaseChanged();
}

// implementation of SQLConnection interface

class ConnectAddress
{
public:
	ConnectAddress (const char * connectAddress)
	{
		const char *server = connectAddress;
		while (*server != 0 && *server != '@') server++;

		if (server[0] != 0 && server[1] != 0)
			_server = server;

		if (*connectAddress != '@')
		{
			int length = server - connectAddress;
			_database.attach(new char [length + 1]);
			memcpy(_database, connectAddress, length);
			_database[length] = 0;
		}
	}

	_cstr_t& server () { return _server; }
	_cstr_t& database () { return _database; }

private:
	_cstr_t _server;
	_cstr_t _database;
};

SQL_METHOD_IMP CInformixConnection::connect (
	pCHAR pszServerName, pCHAR pszUserName, pCHAR pszPassword) SAFE_SQL_METHOD
{
	CLicenseManager::checkLicense();

	TRACE_MESSAGE(getCallback())(traceCONNECT, "connect to %s as %s", pszServerName, pszUserName);

	ConnectAddress connectAddress(pszServerName);

	internalConnect(connectAddress.server(), pszUserName, pszPassword);

	try {
		if (connectAddress.database() != 0)
			checkInformixResult(sqli_db_open(connectAddress.database(), 0));
		refreshFeatures();
		applyWaitOnLocks();
		applyIsolationLevel();
		if (_threadMode == INTERTHREADED_FETCH_OPTIMIZED || _threadMode == INTERTHREADED)
			makeDormant();
	} catch (...) {
		disconnect();
		throw;
	}

	RETURN_SQL_RESULT;
}

SQL_METHOD_IMP CInformixConnection::disconnect () SAFE_SQL_METHOD
{
	if (!isConnected())
		return SQL_SUCCESS;

	TRACE_MESSAGE(getCallback())(traceCONNECT, "disconnect");

	makeCurrent();

	sqli_connect_close(3, 0, 0, 0);
	mint sqlCode = SQLCODE;

	if (sqlCode == IFX_INVALID_TRANSACTION_STATE)
	{
		if (_autoCommit)
			sqlCode = sqli_trans_commit();
		else
			sqlCode = sqli_trans_rollback();

		// Informix Online 5.0 generates on disconnect "Invalid transaction state"
		// error even if no active transactions, so if commit or rollback fails then
		// "database close" have to be called to disconnect from 5.0 server
		if (sqlCode == IFX_NOT_IN_TRANSACTION)
		{
			checkInformixResult(sqli_db_close());
			sqli_connect_close(3, 0, 0, 0);
			sqlCode = SQLCODE;
		}
		else
		{
			checkInformixResult(sqlCode);
			_isInTransaction = false;

			sqli_connect_close(3, 0, 0, 0);
			if ((sqlCode = SQLCODE) == IFX_INVALID_TRANSACTION_STATE)
			{
				checkInformixResult(sqli_db_close());
				sqli_connect_close(3, 0, 0, 0);
				sqlCode = SQLCODE;
			}
		}
	}
	
	checkInformixResult(sqlCode);

	_handle = 0;

	RETURN_SQL_RESULT;
}

SQL_METHOD_IMP CInformixConnection::getSQLCommand (ppSQLCommand ppComm) SAFE_SQL_METHOD
{
	*ppComm = new CInformixCommand(this);
	RETURN_SQL_RESULT;
}

SQL_METHOD_IMP CInformixConnection::getSQLMetaData (ppSQLMetaData ppMeta) SAFE_SQL_METHOD
{
	*ppMeta = new CInformixMetaData(this);
	RETURN_SQL_RESULT;
}

SQL_METHOD_IMP CInformixConnection::setOption (eSQLConnectOption eCOption, INT32 lValue) SAFE_SQL_METHOD
{
	switch (eCOption)
	{
	case eConnAutoCommit:
		setAutoCommit(lValue != 0); break;

	case eConnBlobSize:
		setBlobSize(lValue); break;

	case eConnWaitOnLocks:
		setWaitOnLocks(lValue != 0 ? -1 : 0); break;

	case eConnCommitRetain:
		setCommitRetain(lValue); break;

	case eConnTxnIsoLevel:
		setIsolationLevel(static_cast<eXILType>(lValue)); break;

	case eConnCallBack:
		_callback.setHandler(reinterpret_cast<pfCallBack>(lValue)); break;

	case eConnHostName:
		CInformixConnectionStringParser::getInstance().parse(reinterpret_cast<char *>(lValue), this);
		break;

	case eConnCallBackInfo:
		_callback.setInfo(lValue); break;

	case eConnServerCharSet:
		setClientLocale(reinterpret_cast<char *>(lValue)); break;

	case eConnOnLine:
		setIsOnLine(lValue != 0); break;

	case eConnTrimChar:
		setTrimChar(lValue != 0); break;

	case eConnQualifiedName:
		setQualifiedName(reinterpret_cast<char *>(lValue)); break;

	default:
		return DBXERR_NOTSUPPORTED;
	}

	RETURN_SQL_RESULT;
}

SQL_METHOD_IMP CInformixConnection::getOption (eSQLConnectOption eCOption, pINT32 plValue, INT16 iMaxLength, pINT16 piLength) SAFE_SQL_METHOD
{
	switch (eCOption)
	{
	case eConnAutoCommit:
		*getVerifiedValueDestination(sizeof(INT32), plValue, iMaxLength, piLength) = _autoCommit; break;

	case eConnBlobSize:
		*getVerifiedValueDestination(sizeof(INT32), plValue, iMaxLength, piLength) = _blobSize; break;

	case eConnCommitRetain:
		*getVerifiedValueDestination(sizeof(INT32), plValue, iMaxLength, piLength) = _commitRetain; break;

	case eConnServerVersion:
		strcpy(reinterpret_cast<char *>(getVerifiedValueDestination(strlen(getServerVersion()), plValue, iMaxLength, piLength)), getServerVersion()); break;

	case eConnCallBack:
		*getVerifiedValueDestination(sizeof(INT32), plValue, iMaxLength, piLength) = reinterpret_cast<INT32>(_callback.getHandler()); break;

	case eConnCallBackInfo:
		*getVerifiedValueDestination(sizeof(INT32), plValue, iMaxLength, piLength) = _callback.getInfo(); break;

	case eConnMaxActiveComm:
		*getVerifiedValueDestination(sizeof(INT32), plValue, iMaxLength, piLength) = -1; break;

	case eConnSupportsTransaction:
		*getVerifiedValueDestination(sizeof(INT32), plValue, iMaxLength, piLength) = _supportsTransaction; break;

	case eConnMultipleTransaction:
		*getVerifiedValueDestination(sizeof(INT32), plValue, iMaxLength, piLength) = 0; break;

	case eConnOnLine:
		*getVerifiedValueDestination(sizeof(INT32), plValue, iMaxLength, piLength) = _isOnLine; break;

	case eConnTrimChar:
		*getVerifiedValueDestination(sizeof(INT32), plValue, iMaxLength, piLength) = _trimChar; break;

	case eConnCatalogName:
		strcpy(reinterpret_cast<char *>(getVerifiedValueDestination(strlen(_catalogName), plValue, iMaxLength, piLength)), _catalogName); break;

	case eConnSchemaName:
		strcpy(reinterpret_cast<char *>(getVerifiedValueDestination(strlen(_schemaName), plValue, iMaxLength, piLength)), _schemaName); break;

	case eConnObjectName:
		strcpy(reinterpret_cast<char *>(getVerifiedValueDestination(strlen(_objectName), plValue, iMaxLength, piLength)), _objectName); break;

	case eConnQuotedObjectName:
		strcpy(reinterpret_cast<char *>(getVerifiedValueDestination(strlen(_quotedObjectName), plValue, iMaxLength, piLength)), _quotedObjectName); break;
	}

	RETURN_SQL_RESULT;
}

SQL_METHOD_IMP CInformixConnection::beginTransaction (UINT32 ulTransDesc) SAFE_SQL_METHOD
{
	TRACE_MESSAGE(getCallback())(traceTRANSACT, "BEGIN TRANSACTION");

	CInformixActivityPtr activity = activate(false, true);

	pTransactionDesc pTD = reinterpret_cast<pTransactionDesc>(ulTransDesc);
	if (pTD != 0)
		setIsolationLevel(pTD->eTransIsoLevel, pTD->uCustomIsolation != 0);

	mint status = sqli_trans_begin();
	if (status != IFX_ALREADY_IN_TRANSACTION)
		checkInformixResult(status);

	_isInTransaction = true;

	RETURN_SQL_RESULT;
}

SQL_METHOD_IMP CInformixConnection::commit (UINT32 ulTransDesc) SAFE_SQL_METHOD
{
	TRACE_MESSAGE(getCallback())(traceTRANSACT, "COMMIT");

	CInformixActivityPtr activity = activate(false, true);
	checkInformixResult(sqli_trans_commit());
	_isInTransaction = false;

	RETURN_SQL_RESULT;
}

SQL_METHOD_IMP CInformixConnection::rollback (UINT32 ulTransDesc) SAFE_SQL_METHOD
{
	TRACE_MESSAGE(getCallback())(traceTRANSACT, "ROLLBACK");

	CInformixActivityPtr activity = activate(false, true);
	checkInformixResult(sqli_trans_rollback());
	_isInTransaction = false;

	RETURN_SQL_RESULT;
}

// implementation of IInformixConnection interface

COM_METHOD_IMP(sqlca_s *) CInformixConnection::SQLCA ()
{
	return &_savedConnectionArea;
}

COM_METHOD_IMP(const char *) CInformixConnection::errorMessage ()
{
	return getErrorMessage();
}

SQL_METHOD_IMP CInformixConnection::setTimeOutCallback (long timeOut, TimeOutCallback callback, void *customData) SAFE_SQL_METHOD
{
	CInformixActivityPtr activity = activate(false, true);
		
	if (timeOut >= 0 && callback != 0) {
		_timeOutCallback = callback;
		_timeOutCustomData = customData;
		checkInformixResult(sqlbreakcallback(timeOut, internalTimeOutCallback));
	} else
		checkInformixResult(sqlbreakcallback(timeOut, 0));

	RETURN_SQL_RESULT;
}

COM_METHOD_IMP(bool) CInformixConnection::interruptSQL ()
{
	ENTER_SAFE_CODE;
	if (sqldone() == IFX_SERVER_BUSY)
		return sqlbreak() == 0;
	return true;
	LEAVE_SAFE_CODE;
	return false;
}

COM_METHOD_IMP(bool) CInformixConnection::getLastInsertedSerial8 (void *value)
{
	ENTER_SAFE_CODE;
	ifx_int8 serial;
	ifx_getserial8(&serial);
	if (_typesMapping.int8.logType == fldFLOAT)
		*reinterpret_cast<pDFLOAT>(value) = int8_to_double(&serial);
	else
		int8_to_bcd(&serial, reinterpret_cast<pFMTBcd>(value));
	return true;
	LEAVE_SAFE_CODE;
	return false;
}

COM_METHOD_IMP(const char *) CInformixConnection::serverVersion ()
{
	ENTER_SAFE_CODE;
	return getServerVersion();
	LEAVE_SAFE_CODE;
	return 0;
}

COM_METHOD_IMP(bool) CInformixConnection::isDynamicServer ()
{
	return _isOnLine;
}

COM_METHOD_IMP(int) CInformixConnection::isUniversalServer ()
{
	ENTER_SAFE_CODE;
	return isUniversal() ? 1 : 0;
	LEAVE_SAFE_CODE;
	return -1;
}

COM_METHOD_IMP(int) CInformixConnection::sessionID ()
{
	ENTER_SAFE_CODE;
	if (_sessionID == -1)
	{
		CInformixActivityPtr activity = activate(false, false);
		const char *sqlcmdtxt[] = {
			"select dbinfo('sessionid') from informix.systables where tabid = 1", 0
		};
		ifx_sqlvar_t sqlvar = { 0 };
		sqlvar.sqltype = CINTTYPE;
		sqlvar.sqllen = sizeof(_sessionID);
		sqlvar.sqldata = reinterpret_cast<char *>(&_sessionID);
		ifx_cursor_t cursor = { 0 };
		checkInformixResult(sqli_slct(ESQLINTVERSION, &cursor, StringArrayCast(sqlcmdtxt), 0, 0, 1, &sqlvar, 0, 0, 0, 0));
	}
	LEAVE_SAFE_CODE;
	return _sessionID;
}

// implementation of IErrorRegister interface

void CInformixConnection::registerError (const char *errorMessage)
{
	TRACE_MESSAGE(getCallback())(traceERROR, errorMessage);
}

// private methods

void CInformixConnection::applyIsolationLevel ()
{
	if (!_supportsTransaction || !_setIsolation)
		return;

	ENTER_SAFE_CODE;
	char setIsolationSQL[128];
	sprintf(setIsolationSQL, "SET ISOLATION TO %s", _isolationLevel);
	checkInformixResult(sqli_exec_immed(setIsolationSQL));
	return;
	LEAVE_SAFE_CODE;

	_setIsolation = false;
	registerError("Cannot set isolation level");
}

void CInformixConnection::applyWaitOnLocks ()
{
	ENTER_SAFE_CODE;
	switch (_waitOnLocks)
	{
	case -1:
		checkInformixResult(sqli_exec_immed("SET LOCK MODE TO WAIT")); break;
	case 0:
		checkInformixResult(sqli_exec_immed("SET LOCK MODE TO NOT WAIT")); break;
	default:
		char setLockSQL[100];
		snprintf(setLockSQL, sizeof(setLockSQL), "SET LOCK MODE TO WAIT %d", _waitOnLocks);
		checkInformixResult(sqli_exec_immed(setLockSQL));
	}
	return;
	LEAVE_SAFE_CODE;

	throw CInformixDriverError("Cannot set lock mode (see log for details)");
}

#ifdef WIN32

#include <windows.h>

long g_handleCounter = 0;

void CInformixConnection::internalConnect (_cstr_t& server, char *userName, char *password)
{
	char handle[19];
	sprintf(handle, "_%p_%x", this, InterlockedIncrement(&g_handleCounter));

	_cstr_t clientLocaleStr;
	if (_clientLocale != 0) {
		clientLocaleStr.attach(_cstr_t::concat("CLIENT_LOCALE=", _clientLocale));
		ifx_putenv(clientLocaleStr);
	}

	_cstr_t databaseLocaleStr;
	if (_databaseLocale != 0) {
		databaseLocaleStr.attach(_cstr_t::concat("DB_LOCALE=", _databaseLocale));
		ifx_putenv(databaseLocaleStr);
	}

	ifx_setenv("WIN32USER", userName);
	ifx_setenv("WIN32PASS", password);

	if (server == 0)
		server.attach(_cstr_t::concat("@", ifx_getenv("INFORMIXSERVER")));

	sqli_connect_open(ESQLINTVERSION, 0, server, handle, 0, 1);
	saveConnectionArea();
	checkInformixResult();

	_handle = handle;
}

#else

void CInformixConnection::internalConnect (_cstr_t& server, char *userName, char *password)
{
	char handle[19];
	sprintf(handle, "_%p", this);

	if (_clientLocale != 0)
		ifx_setenv("CLIENT_LOCALE", _clientLocale);

	if (_databaseLocale != 0)
		ifx_setenv("DB_LOCALE", _databaseLocale);

	ifx_user_t user = { userName, password };
	ifx_conn_t conn = { IFX_CONN_TYPE_USER_AUTH, &user };

	if (server == 0)
		server.attach(_cstr_t::concat("@", ifx_getenv("INFORMIXSERVER")));

	sqli_connect_open(ESQLINTVERSION, 0, server, handle, &conn, 1);
	saveConnectionArea();
	checkInformixResult();

	_handle = handle;
}

#endif

void CInformixConnection::internalTimeOutCallback (int status)
{
	char *connectionName = ifx_getcur_conn_name();
	CInformixConnection *connection = reinterpret_cast<CInformixConnection *>(strtol(++connectionName, 0, 16));
	connection->_timeOutCallback(connection->_timeOutCustomData, status);
}

bool CInformixConnection::isConnected ()
{
	return _handle != 0;
}

void CInformixConnection::makeCurrent ()
{
	if (_threadMode == ONE_PER_THREAD)
		return;

	char *current = ifx_getcur_conn_name();

	if (current == 0 || strcmp(_handle, current) != 0) {
		sqli_connect_set(0, _handle, 0);
		checkInformixResult();
	}
}

void CInformixConnection::makeDormant ()
{
	char *current = ifx_getcur_conn_name();

	if (current != 0 && strcmp(_handle, current) == 0) {
		sqli_connect_set(0, _handle, 1);
		checkInformixResult();
	}
}

void CInformixConnection::refreshFeatures ()
{
	_serverVersion = 0;
	_isUniversalServer = -1;
	_sessionID = -1;
	_isInTransaction = false;

	_supportsTransaction = false;
	_isAnsiDatabase = false;
	_isOnLine = false;
	_setIsolation = false;

	int cExceptions;
	ifx_hostvar_t hostVars[3];
	memset(hostVars, 0, sizeof(hostVars));
	sqli_mt_hostbind(ESQLINTVERSION, &hostVars[0], 1, CINTTYPE, 0, sizeof(cExceptions), 0);
	hostVars[0].hostaddr = reinterpret_cast<char *>(&cExceptions);
	sqli_diag_get(ESQLINTVERSION, hostVars, -1);

	sqlca_s *psqlca = &sqlca;

	for (int i = 1; i <= cExceptions; i++)
	{
		// get SQLSTATE diagnostic value
		char szSQLState[6];
		sqli_mt_hostbind(ESQLINTVERSION, &hostVars[0], 3, CCHARTYPE, 0, sizeof(szSQLState), 0);
		hostVars[0].hostaddr = szSQLState;
		sqli_diag_get(ESQLINTVERSION, hostVars, i);

		// check if database supports transactions
		if ((strcmp(szSQLState, "01I01") == 0) && (psqlca->sqlwarn.sqlwarn1 == 'W'))
			_supportsTransaction = true;

		// check if database is in ANSI logging mode
		else if ((strcmp(szSQLState, "01I03") == 0) && (psqlca->sqlwarn.sqlwarn2 == 'W'))
			_isAnsiDatabase = true;

		// check if server is not Informix-SE
		else if ((strcmp(szSQLState, "01I04") == 0) && (psqlca->sqlwarn.sqlwarn3 == 'W')) {
			_isOnLine = true;
			_setIsolation = true;
		}
	}
}

void CInformixConnection::saveConnectionArea ()
{
	_savedConnectionArea = sqlca;
}

void CInformixConnection::setQualifiedName (char *value)
{
	_qualifiedName = value;

	// parse qualified name for catalog name, schema name and object name
	//
	char *pos = _qualifiedName;
	while (*pos != 0 && *pos != '.' && *pos != ':') pos++;
	if (*pos == ':') {
		*pos = 0;
		_catalogName = _qualifiedName;
		_schemaName = ++pos;
	} else {
		_catalogName = "";
		_schemaName = _qualifiedName;
	}
	while (*pos != 0 && *pos != '.') pos++;
	if (*pos == '.') {
		*pos = 0;
		_objectName = ++pos;
	}
	else {
		_objectName = _schemaName;
		_schemaName = "";
	}

	// create valid quoted name
	//
	_quotedObjectName.attach(new char [strlen(_catalogName) + strlen(_schemaName) + strlen(_objectName) + sizeof(":\"\".")]);
	if (*_catalogName != 0)
		strcat(strcpy(_quotedObjectName, _catalogName), ":");
	else
		*_quotedObjectName = 0;
	if (*_schemaName != 0)
		strcat(strcat(strcat(_quotedObjectName, "\""), _schemaName), "\".");
	strcat(_quotedObjectName, _objectName);
}


// CInformixConnection::CConnectionActivity

CInformixConnection::CConnectionActivity::CConnectionActivity (CInformixConnection *connection)
{
	(_connection = connection)->_hasActivity = true;
}

CInformixConnection::CConnectionActivity::~CConnectionActivity ()
{
	_connection->_hasActivity = false;
}


// CInformixConnection::CAutoDormantDecorator

CInformixConnection::CAutoDormantDecorator::CAutoDormantDecorator (CInformixConnection *connection, CInformixActivityPtr& activity)
{
	_connection = connection;
	_activity = activity;
}

CInformixConnection::CAutoDormantDecorator::~CAutoDormantDecorator ()
{
	try {
		_connection->makeDormant();
	} catch (...) {
	}
}


// CInformixConnection::CAutoCommitDecorator

CInformixConnection::CAutoCommitDecorator::CAutoCommitDecorator (CInformixActivityPtr& activity)
{
	_activity = activity;
}

CInformixConnection::CAutoCommitDecorator::~CAutoCommitDecorator ()
{
	try {
		sqli_trans_commit();
	} catch (...) {
	}
}


// CInformixConnection::CConnectionAreaDecorator

CInformixConnection::CConnectionAreaDecorator::CConnectionAreaDecorator (CInformixConnection *connection, CInformixActivityPtr& activity)
{
	_connection = connection;;
	_activity = activity;
}

CInformixConnection::CConnectionAreaDecorator::~CConnectionAreaDecorator ()
{
	_connection->saveConnectionArea();
}


/////////////////////////////////////////////////////////////////////////////
// CInformixConnectionStringParser

CInformixConnectionStringParser CInformixConnectionStringParser::_instance;

CInformixConnectionStringParser& CInformixConnectionStringParser::getInstance ()
{
	return _instance;
}

void CInformixConnectionStringParser::parse (char *connectionString, CInformixConnection *connectionContext)
{
	char *name, *_name, *value, *_value, separator;

	while (*connectionString != 0)
	{
		// extract name
		while (isspace(*connectionString) != 0) connectionString++;
		name = _name = connectionString;
		while (*connectionString != '=' && *connectionString != ';' && *connectionString != 0)
			if (isspace(*connectionString++) == 0)
				_name = connectionString;
		if ((separator = *connectionString) != 0)
			connectionString++;
		*_name = 0;

		// value without a key is interpreted as Informix server name
		if (separator != '=') {
			ifx_setenv("INFORMIXSERVER", name);
			continue;
		}
		
		// extract value
		while (isspace(*connectionString) != 0) connectionString++;
		value = _value = connectionString;
		while (*connectionString != ';' && *connectionString != 0)
			if (isspace(*connectionString++) == 0)
				_value = connectionString;
		if (*connectionString != 0)
			connectionString++;
		*_value = 0;

		// dispatch connection string parameter
		OptionHandlers::Element *element = _optionHandlers.get(_cstr_t::upperCase(name));
		if (element != 0)
			element->value(connectionContext, value);
		else
			ifx_setenv(name, value);
	}
}

CInformixConnectionStringParser::CInformixConnectionStringParser (): _optionHandlers(23)
{
	_optionHandlers.add("AUTOCOMMIT")->value = setAutoCommit;
	_optionHandlers.add("BLOBSIZE")->value = setBlobSize;
	_optionHandlers.add("CLIENT_LOCALE")->value = setClientLocale;
	_optionHandlers.add("COMMITRETAIN")->value = setCommitRetain;
	_optionHandlers.add("DB_LOCALE")->value = setDatabaseLocale;
	_optionHandlers.add("DECIMAL")->value = setDecimal;
	_optionHandlers.add("INT8")->value = setInt8;
	_optionHandlers.add("LVARCHAR")->value = setLVarChar;
	_optionHandlers.add("MONEY")->value = setMoney;
	_optionHandlers.add("THREAD MODE")->value = setThreadMode;
	_optionHandlers.add("TRIM CHAR")->value = setTrimChar;
	_optionHandlers.add("WAITONLOCKS")->value = setWaitOnLocks;
}

void CInformixConnectionStringParser::setAutoCommit (CInformixConnection *connectionContext, char *value)
{
	connectionContext->setAutoCommit(strcmp(_cstr_t::upperCase(value), "TRUE") == 0);
}

void CInformixConnectionStringParser::setBlobSize (CInformixConnection *connectionContext, char *value)
{
	connectionContext->setBlobSize(atoi(value));
}

void CInformixConnectionStringParser::setClientLocale (CInformixConnection *connectionContext, char *value)
{
	connectionContext->setClientLocale(value);
}

void CInformixConnectionStringParser::setCommitRetain (CInformixConnection *connectionContext, char *value)
{
	connectionContext->setCommitRetain(strcmp(_cstr_t::upperCase(value), "TRUE") == 0);
}

void CInformixConnectionStringParser::setDatabaseLocale (CInformixConnection *connectionContext, char *value)
{
	connectionContext->setDatabaseLocale(value);
}

void CInformixConnectionStringParser::getNumericMapping (char *value, DataType& dataType)
{
	if (strcmp(_cstr_t::upperCase(value), "BCD") == 0)
		dataType = DataType(fldBCD, 0);
	else if (strcmp(value, "FMTBCD") == 0)
		dataType = DataType(fldFMTBCD, 0);
	else if (strcmp(value, "FLOAT") == 0)
		dataType = DataType(fldFLOAT, 0);
	else if (strcmp(value, "CURRENCY") == 0)
		dataType = DataType(fldFLOAT, fldstMONEY);
}

void CInformixConnectionStringParser::setDecimal (CInformixConnection *connectionContext, char *value)
{
	getNumericMapping(value, connectionContext->getTypesMapping()->decimal);
}

void CInformixConnectionStringParser::setInt8 (CInformixConnection *connectionContext, char *value)
{
	getNumericMapping(value, connectionContext->getTypesMapping()->int8);
}

void CInformixConnectionStringParser::setLVarChar (CInformixConnection *connectionContext, char *value)
{
	if (strcmp(_cstr_t::upperCase(value), "STRING") == 0)
		connectionContext->getTypesMapping()->lvarchar = DataType(fldZSTRING, 0);
	else if (strcmp(value, "BLOB") == 0)
		connectionContext->getTypesMapping()->lvarchar = DataType(fldBLOB, fldstFMTMEMO);
}

void CInformixConnectionStringParser::setMoney (CInformixConnection *connectionContext, char *value)
{
	getNumericMapping(value, connectionContext->getTypesMapping()->money);
}

void CInformixConnectionStringParser::setThreadMode (CInformixConnection *connectionContext, char *value)
{
	if (strcmp(_cstr_t::upperCase(value), "ONE PER THREAD") == 0)
		connectionContext->setThreadMode(CInformixConnection::ONE_PER_THREAD);
	else if (strcmp(value, "MANY PER THREAD") == 0)
		connectionContext->setThreadMode(CInformixConnection::MANY_PER_THREAD);
	else if (strcmp(value, "INTERTHREADED (FETCH OPTIMIZED)") == 0)
		connectionContext->setThreadMode(CInformixConnection::INTERTHREADED_FETCH_OPTIMIZED);
	else if (strcmp(value, "INTERTHREADED") == 0)
		connectionContext->setThreadMode(CInformixConnection::INTERTHREADED);
}

void CInformixConnectionStringParser::setTrimChar (CInformixConnection *connectionContext, char *value)
{
	connectionContext->setTrimChar(strcmp(_cstr_t::upperCase(value), "TRUE") == 0);
}

void CInformixConnectionStringParser::setWaitOnLocks (CInformixConnection *connectionContext, char *value)
{
	if (strcmp(_cstr_t::upperCase(value), "TRUE") == 0)
		connectionContext->setWaitOnLocks(-1);
	else if (strcmp(value, "FALSE") == 0)
		connectionContext->setWaitOnLocks(0);
	else
		connectionContext->setWaitOnLocks(atoi(value));
}
